#include <iostream>
#include <string>
using namespace std;
#include <assert.h>

#define __STDC_CONSTANT_MACROS

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
};

#include "SDL.h"
#include "SDL_thread.h"

#include "PacketQueue.h"

#define WRITE_PCM 0

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

#define PRINT_RETURN_1(x) do{printf("ERROR: "#x"\n"); getchar(); return 1;}while(false)


PacketQueue audioQ;
PacketQueue videoQ;

int quit = 0;
//PacketQueue audioq;
#if WRITE_PCM
FILE *pcmfile;
#endif
struct SwrContext *au_convert_ctx;
int out_buffer_size = 0;


void SaveFrame(AVFrame *pFrame, int width, int height, int frameIndex) {
    char szFileName[32] = {0};
    sprintf_s(szFileName, 32, "frame%d.ppm", frameIndex);

    FILE *pFile = fopen(szFileName, "wb");

    if(pFile == NULL)
        return;

    fprintf(pFile, "P6\n%d %d\n255\n", width, height);

    for(int y = 0; y < height; y++) {
        fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
    }

    fclose(pFile);
}

void SaveFrame2(AVPicture *pPict, int width, int height, int frameIndex) {
    char szFileName[32] = {0};
    sprintf_s(szFileName, 32, "frame%d.ppm", frameIndex);

    FILE *pFile = fopen(szFileName, "wb");

    if(pFile == NULL)
        return;

    fprintf(pFile, "P6\n%d %d\n255\n", width, height);

    for(int y = 0; y < height; y++) {
        fwrite(pPict->data[0] + y * pPict->linesize[0], 1, width * 3, pFile);
    }

    fclose(pFile);
}


int audio_decode_frame(AVCodecContext *pCodecCtx, uint8_t *audio_buf, int buf_size) {
    static AVPacket pkt;
    static uint8_t *audio_pkt_data = NULL;
    static int audio_pkt_size = 0;
    static AVFrame frame;

    int len1;

    for(;;) {
        while(audio_pkt_size > 0) {
            int got_frame = 0;
            len1 = avcodec_decode_audio4(pCodecCtx, &frame, &got_frame, &pkt);

            if(len1 < 0) {
                /* if error, skip frame */
                audio_pkt_size = 0;
                break;
            }

            audio_pkt_data += len1;
            audio_pkt_size -= len1;
            int data_size = 0;

            if(got_frame) {


                if(out_buffer_size > 0) {
                    data_size = out_buffer_size;
                } else {
                    data_size = av_samples_get_buffer_size(NULL,
                                                           pCodecCtx->channels,
                                                           frame.nb_samples,
                                                           pCodecCtx->sample_fmt,
                                                           1);
                }

                swr_convert(au_convert_ctx, &audio_buf, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)frame.data , frame.nb_samples);
                //assert(data_size <= buf_size);
                //memcpy(audio_buf, frame.data[0], data_size);
#if WRITE_PCM
                fwrite(audio_buf, 1, data_size, pcmfile);
#endif
            } else {
                continue;
            }

            //             if(data_size <= 0) {
            //                 /* No data yet, get more frames */
            //                 continue;
            //             }

            /* We have data, return it and come back for more later */
            return data_size;
        }

        if(pkt.data)
            av_free_packet(&pkt);

        if(quit) {
            return -1;
        }

//         if(packet_queue_get(&audioq, &pkt, 1) < 0) {
//             return -1;
//         }

		audioQ.Pop(&pkt, true);

        audio_pkt_data = pkt.data;
        audio_pkt_size = pkt.size;
    }
}


void SDLCALL audio_callback(void *userdata, Uint8 *stream, int len) {
    AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
    int len1, audio_size;

    static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
    static unsigned int audio_buf_size = 0;
    static unsigned int audio_buf_index = 0;

    while(len > 0) {
        if(audio_buf_index >= audio_buf_size) {
            /* We have already sent all our data; get more */
            audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));

            if(audio_size < 0) {
                /* If error, output silence */
                audio_buf_size = 1024; // arbitrary?
                memset(audio_buf, 0, audio_buf_size);
            } else {
                audio_buf_size = audio_size;
            }

            audio_buf_index = 0;
        }

        len1 = audio_buf_size - audio_buf_index;

        if(len1 > len)
            len1 = len;

        memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
        len -= len1;
        stream += len1;
        audio_buf_index += len1;
    }

}

int main(int argc, char *argv[]) {
    SDL_Event event;

    if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_TIMER))
        PRINT_RETURN_1("SDL_Init");

    av_register_all();

    AVFormatContext *pFormatCtx = NULL;

    if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0)
        PRINT_RETURN_1("avformat_open_input");

    if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
        PRINT_RETURN_1("avformat_find_stream_info");

    for(unsigned int  i = 0; i < pFormatCtx->nb_streams; i++) {
        av_dump_format(pFormatCtx, i, argv[1], 0);
        cout << endl << "===========================================================" << endl;
    }

    unsigned int videoStream = -1;
    unsigned int audioStream = -1;

    for(unsigned int  i = 0; i < pFormatCtx->nb_streams; i++) {
        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && videoStream == -1)
            videoStream = i;

        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audioStream == -1)
            audioStream = i;
    }


    if(videoStream == -1 && audioStream == -1)
        PRINT_RETURN_1("videoStream == -1 && audioStream == -1");

    AVCodecContext *pVideoCodecCtx = NULL;
    AVCodec *pVideoCodec = NULL;
    struct SwsContext *sws_ctx = NULL;
    SDL_Rect rect;
    SDL_Overlay *bmp = NULL;

    AVCodecContext *pAuidoCodecCtx = NULL;
    AVCodec *pAudioCodec = NULL;

    if(videoStream != -1) {
        pVideoCodec = avcodec_find_decoder(pFormatCtx->streams[videoStream]->codec->codec_id);

        if(pVideoCodec == NULL)
            PRINT_RETURN_1("avcodec_find_decoder");

        pVideoCodecCtx = avcodec_alloc_context3(pVideoCodec);

        if(avcodec_copy_context(pVideoCodecCtx, pFormatCtx->streams[videoStream]->codec) != 0)
            PRINT_RETURN_1("avcodec_copy_context");

        cout << "video codec info:" << endl
             << "\tname:" << pVideoCodecCtx->codec_descriptor->name << endl
             << "\tlong name:" << pVideoCodecCtx->codec_descriptor->long_name << endl
             << "\tid:" << pVideoCodecCtx->codec_id << endl
             << endl;

        if(avcodec_open2(pVideoCodecCtx, pVideoCodec, NULL) != 0)
            PRINT_RETURN_1("avcodec_open2");

        SDL_Surface *screen = SDL_SetVideoMode(pVideoCodecCtx->width, pVideoCodecCtx->height, 0, 0);
        bmp = SDL_CreateYUVOverlay(pVideoCodecCtx->width, pVideoCodecCtx->height, SDL_YV12_OVERLAY, screen);

        sws_ctx = sws_getContext(pVideoCodecCtx->width, pVideoCodecCtx->height,
                                 pVideoCodecCtx->pix_fmt, pVideoCodecCtx->width, pVideoCodecCtx->height,
                                 AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL);

        rect.x = 0;
        rect.y = 0;
        rect.w = pVideoCodecCtx->width;
        rect.h = pVideoCodecCtx->height;
    }


    if(audioStream != -1) {
        pAudioCodec = avcodec_find_decoder(pFormatCtx->streams[audioStream]->codec->codec_id);

        if(pAudioCodec == NULL)
            PRINT_RETURN_1("avcodec_find_decoder");

        pAuidoCodecCtx = avcodec_alloc_context3(pAudioCodec);

        if(avcodec_copy_context(pAuidoCodecCtx, pFormatCtx->streams[audioStream]->codec) != 0)
            PRINT_RETURN_1("avcodec_copy_context");

        cout << "audio codec info:" << endl
             << "\tname:" << pAuidoCodecCtx->codec_descriptor->name << endl
             << "\tlong name:" << pAuidoCodecCtx->codec_descriptor->long_name << endl
             << "\tid:" << pAuidoCodecCtx->codec_id << endl
             << endl;

        if(avcodec_open2(pAuidoCodecCtx, pAudioCodec, NULL) != 0)
            PRINT_RETURN_1("avcodec_open2");

        uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;
        AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
        int out_samples_per_channel = pAuidoCodecCtx->frame_size;
        int out_sample_rate = 44100;
        int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);

        if(out_samples_per_channel > 0) {
            out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_samples_per_channel, out_sample_fmt, 1);

            if(out_buffer_size < 0)
                PRINT_RETURN_1("av_samples_get_buffer_size");
        }


        int in_channel_layout = av_get_default_channel_layout(pAuidoCodecCtx->channels);

        SDL_AudioSpec spec;
        SDL_AudioSpec wanted_spec;
        wanted_spec.freq = out_sample_rate;
        wanted_spec.format = AUDIO_S16SYS;
        wanted_spec.channels = out_channels;
        wanted_spec.silence = 0;
        wanted_spec.samples = out_samples_per_channel;
        wanted_spec.callback = audio_callback;
        wanted_spec.userdata = pAuidoCodecCtx;

        if(SDL_OpenAudio(&wanted_spec, &spec) < 0)
            PRINT_RETURN_1("SDL_OpenAudio");


        au_convert_ctx = swr_alloc();
        au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt, out_sample_rate,
                                            in_channel_layout, pAuidoCodecCtx->sample_fmt , pAuidoCodecCtx->sample_rate, 0, NULL);

        swr_init(au_convert_ctx);

#if WRITE_PCM
        pcmfile = fopen("output.pcm", "wb+");
#endif
        //packet_queue_init(&audioq);
        SDL_PauseAudio(0);
    }


    //AVFrame *pFrameRGB = av_frame_alloc();
    //  int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
    //  if(numBytes < 0) {
    //      return -1;
    //  }
    //  uint8_t *buffer = (uint8_t*)av_malloc(numBytes);
    //  avpicture_fill((AVPicture*)pFrameRGB, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);


    int i = 0;
    int frameFinished = 0;
    AVPacket packet;
    AVFrame *pFrame = av_frame_alloc();

    while(av_read_frame(pFormatCtx, &packet) >= 0) {
		
        if(packet.stream_index == videoStream) {

            avcodec_decode_video2(pVideoCodecCtx, pFrame, &frameFinished, &packet);

            if(frameFinished > 0) {

                SDL_LockYUVOverlay(bmp);

                AVPicture pict;
                pict.data[0] = bmp->pixels[0];
                pict.data[1] = bmp->pixels[2];
                pict.data[2] = bmp->pixels[1];

                pict.linesize[0] = bmp->pitches[0];
                pict.linesize[1] = bmp->pitches[2];
                pict.linesize[2] = bmp->pitches[1];

                sws_scale(sws_ctx, (const uint8_t *const *)pFrame->data,
                          pFrame->linesize, 0, pVideoCodecCtx->height,
                          pict.data, pict.linesize);

                SDL_UnlockYUVOverlay(bmp);

                SDL_DisplayYUVOverlay(bmp, &rect);

                //              if( i++ < 100) {
                //                  SaveFrame2(&pict, pVideoCodecCtx->width, pVideoCodecCtx->height, i);
                //              }
            }

            av_free_packet(&packet);
        } else if(packet.stream_index == audioStream) {
            //packet_queue_put(&audioq, &packet);
			audioQ.Push(&packet);
        } else {
            av_free_packet(&packet);
        }


        SDL_PollEvent(&event);

        switch(event.type) {
            case SDL_QUIT:
                quit = 1;
                SDL_Quit();
                exit(0);
                break;

            default:
                break;
        }
    }


    getchar();
#if WRITE_PCM
    fclose(pcmfile);
#endif
    // free
    //

    //     av_free(buffer);
    //  av_free(pFrameRGB);
    if(pVideoCodecCtx)
        avcodec_free_context(&pVideoCodecCtx);

    if(pFrame)
        av_free(pFrame);

    if(pAuidoCodecCtx)
        avcodec_free_context(&pAuidoCodecCtx);

    if(pFormatCtx)
        avformat_close_input(&pFormatCtx);

    return 0;
}